package com.minishift.android.utils;

import java.io.File;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.util.LinkedHashMap;
import java.util.concurrent.ExecutionException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.app.ActivityManager;
import android.app.Application;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.Looper;
import android.support.v4.util.LruCache;

/**
 * 
 * this class is image cache manager class.
 * @author Sirier
 * 
 */
public class XImageCache {

	private static XImageCache cacheManager;
	private static final String IMAGE_CACHE_TAG = "image_cache";
	private static final long DISK_CACHE_SIZE = 1024 * 1024 * 10;
	private static final String DISK_CACHE_SYBDIR = "thumbnails";
	private static final int SOFT_CACHE_SIZE = 15;

	private static Application application;
	private LruCache<String, Bitmap> memoryCache;
	private LinkedHashMap<String, SoftReference<Bitmap>> softCache;
	private XDiskLruCache diskCache;
	private final Object diskCacheLock = new Object();
	private boolean diskCacheStarting = true;

	/**
	 * this method will get only object of XImageCache.
	 * @param Context: current application.
	 * @return InitAction: only object of XImageCache.
	 */
	public static XImageCache form(Context context){
		if(Looper.myLooper() != Looper.getMainLooper()){
			throw new RuntimeException("Cannot instantiate outside UI thread.");
		}
		if(application == null){
			application = (Application) context.getApplicationContext();
		}
		if(cacheManager == null){
			cacheManager = new XImageCache();
		}
		return cacheManager;
	}
	
	/**
	 * construction method
	 */
	public XImageCache() {
		initMemoryCache();
		initDiskCache();
	}

	/**
	 * this method will initialiaze memory cache.
	 */
	private void initMemoryCache() {
		final int memoryClass = ((ActivityManager) application
				.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
		final int cacheSize = 1024 * 1024 * memoryClass / 8;
		memoryCache = new LruCache<String, Bitmap>(cacheSize) {
			@Override
			protected int sizeOf(String key, Bitmap value) {
				if(value != null){
					return value.getRowBytes() * value.getHeight();
				}else{
					return 0;
				}
			}

			@Override
			protected void entryRemoved(boolean evicted, String key,
					Bitmap oldValue, Bitmap newValue) {
				if(oldValue != null){
					softCache.put(key, new SoftReference<Bitmap>(oldValue));
				}
			}
			
		};
		
		softCache = new LinkedHashMap<String, SoftReference<Bitmap>>(SOFT_CACHE_SIZE, 0.75f, true){
			private static final long serialVersionUID = 6040103833179403725L;
			@Override
			protected boolean removeEldestEntry(
					Entry<String, SoftReference<Bitmap>> eldest) {
				if(size() > SOFT_CACHE_SIZE){
					return true;
				}
				return false;
			}
			
		};
	}

	/**
	 * this method will initialize disk cache.
	 */
	private void initDiskCache() {
		File cacheDir = new File(getDiskCacheDir());
		new InitDiskCacheTask().execute(cacheDir);
	}

	/**
	 * this method will add the bitmap to cache.
	 * @param String: bitmap's id or url. 
	 * @param Bitmap: will be added image.
	 */
	public void addBitmapToCache(String key, Bitmap bitmap) {
		if(bitmap != null){
			synchronized (memoryCache) {		
				if (null == getBitmapFromMemoryCache(key)) {
					memoryCache.put(key, bitmap);
				}
			}
			synchronized (diskCacheLock) {
				if (diskCache != null && getBitmpFromDiskCache(key) == null) {
					diskCache.put(key, bitmap);
				}
			}		
		}
	}

	/**
	 * this method will get resource image from cache.
	 * @param String: image id.
	 * @return Bitmap: resource image.
	 */
	public Bitmap getBitmapFromCache(String key){
		try {
			return new GetHttpBitmapTask().execute(key).get();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * this method will get network image from cache.
	 * @param String: image url.
	 * @return Bitmap: network image.
	 */
	public Bitmap getBitmapFromCache(int key){
		try {
			return new GetResourceBitmapTask().execute(key).get();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * this method will get image from memory cache.
	 * @param String: image's id or url.
	 * @return Bitmap: image.
	 */
	public Bitmap getBitmapFromMemoryCache(String key) {
		Bitmap bitmap = null;
		synchronized (memoryCache) {
			bitmap = memoryCache.get(key);
			if(bitmap != null){
				memoryCache.remove(key);
				memoryCache.put(key, bitmap);
				return bitmap;
			}
		}
		synchronized (softCache) {
			SoftReference<Bitmap> reference = softCache.get(key);
			if(reference != null){
				bitmap = reference.get();
				if(bitmap != null){
					memoryCache.put(key, bitmap);
					softCache.remove(key);
					return bitmap;
				}else{
					softCache.remove(key);
				}
			}
		}
		return null;
	}

	/**
	 * this method will get image from disk cache.
	 * @param String: image's id or url.
	 * @return Bitmap: image.
	 */
	public Bitmap getBitmpFromDiskCache(String key) {
		synchronized (diskCacheLock) {
			while (diskCacheStarting) {
				try {
					diskCacheLock.wait();
				} catch (InterruptedException e) {

				}
			}
			if (null != diskCache.get(key)) {
				return diskCache.get(key);
			}
		}
		return null;
	}

	/**
	 * this method will clear cache.
	 */
	public void clearCache(){
		softCache.clear();
		new File(getDiskCacheDir()).delete();
	}
	
	/**
	 * this method will get disk cache dir.
	 * @return String: disk cache dir.
	 */
	private String getDiskCacheDir() {
		File cachePath = null;
		if (Environment.getExternalStorageState().endsWith(
				Environment.MEDIA_MOUNTED)) {
			cachePath = Environment.getExternalStorageDirectory();
		}
		if (cachePath != null) {
			return cachePath.toString() + File.separator
					+ DISK_CACHE_SYBDIR;
		}
		return "";
	}

	/**
	 * this method will decode resource image from resource.
	 * @param Resources: system resource.
	 * @param int: image's id.
	 * @param int: toWidth.
	 * @param int: toHeight.
	 * @return Bitmap: to bitmap.
	 */
	private static Bitmap decodeSampledBitmapFromResource(Resources res,
			int resId, int reqWidth, int reqHeight) {
		final BitmapFactory.Options options = new BitmapFactory.Options();
		options.inJustDecodeBounds = true;
		BitmapFactory.decodeResource(res, resId, options);
		options.inSampleSize = calculateInSampleSize(options, reqWidth,
				reqHeight);
		options.inJustDecodeBounds = false;
		return BitmapFactory.decodeResource(res, resId, options);
	}

	/**
	 * this method will count the bitmap's size.
	 * @param Options: options of bitmap factory.
	 * @param int: fromWidth.
	 * @param int: fromHeight.
	 * @return int: size.
	 */
	public static int calculateInSampleSize(BitmapFactory.Options options,
			int reqWidth, int reqHeight) {
		final int height = options.outHeight;
		final int width = options.outWidth;
		int inSampleSize = 1;

		if (height > reqHeight || width > reqWidth) {
			final int heightRatio = Math.round((float) height
					/ (float) reqHeight);
			final int widthRatio = Math.round((float) width / (float) reqWidth);
			inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
		}
		return inSampleSize;
	}

	/**
	 * 
	 * this class is initializing disk cache class.
	 * @author Sirier
	 *
	 */
	class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
		@Override
		protected Void doInBackground(File... params) {
			synchronized (diskCacheLock) {
				File cacheDir = params[0];
				diskCache = XDiskLruCache.openCache(application, cacheDir,
						DISK_CACHE_SIZE);
				diskCacheStarting = false;
				diskCacheLock.notifyAll();
			}
			return null;
		}

	}
	
	/**
	 * 
	 * this class is getting image from resource class.
	 * @author Sirier
	 *
	 */
	public class GetResourceBitmapTask extends AsyncTask<Integer, Void, Bitmap> {
		@Override
		protected Bitmap doInBackground(Integer... params) {
			final String key = String.valueOf(params[0]);
			Bitmap bitmap = getBitmapFromMemoryCache(key);
			if(null == bitmap){
				bitmap = getBitmpFromDiskCache(key);
				if(null == bitmap){;
					bitmap = decodeSampledBitmapFromResource(application.getResources(), params[0], 100, 100);
				}
			}
			addBitmapToCache(key, bitmap);
			return bitmap;
		}
	}
	
	/**
	 * 
	 * this class is getting image from network class.
	 * @author Sirier
	 *
	 */
	public class GetHttpBitmapTask extends AsyncTask<String, Void, Bitmap> {
		@Override
		protected Bitmap doInBackground(String... params) {
			final String key = params[0];
			Bitmap bitmap = getBitmapFromMemoryCache(key);
			if(null == bitmap){
				bitmap = getBitmpFromDiskCache(key);
				if(null == bitmap){
					bitmap = GetImageFromHttp.downloadBitmap(key);
				}
			}
			addBitmapToCache(key, bitmap);

			return bitmap;
		}
	}
	
	/**
	 * 
	 * this class is getting image from network helper class.
	 * @author Sirier
	 *
	 */
	public static class GetImageFromHttp{
		public static Bitmap downloadBitmap(String url){
			
			final HttpClient client = new DefaultHttpClient();
			final HttpGet get = new HttpGet(url);
	
			try {
				HttpResponse response = client.execute(get);
				final int statusCode = response.getStatusLine().getStatusCode();
				if(statusCode != HttpStatus.SC_OK){
					XLog.i(IMAGE_CACHE_TAG, "NetWork is disconnect!");
					return null;
				}
				final HttpEntity entity = response.getEntity();
				if(null != entity){
					InputStream is = null;
					try{
						is = entity.getContent();
						FlushedInputStream fis = new FlushedInputStream(is);
						return BitmapFactory.decodeStream(fis);
					}finally{
						if(is != null){
							is.close();
							is = null;
						}
						entity.consumeContent();
					}
				}
			} catch (ClientProtocolException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}finally{
				client.getConnectionManager().shutdown();
			}
			return null;
		}
	}
	
	/**
	 * 
	 * this class is getting image from network control class.
	 * @author Sirier
	 *
	 */
	public static class FlushedInputStream extends FilterInputStream {  
		
        public FlushedInputStream(InputStream inputStream) {  
            super(inputStream);  
        }  
                                                         
        @Override  
        public long skip(long n) throws IOException {  
            long totalBytesSkipped = 0L;  
            while (totalBytesSkipped < n) {  
                long bytesSkipped = in.skip(n - totalBytesSkipped);  
                if (bytesSkipped == 0L) {  
                    int b = read();  
                    if (b < 0) {  
                        break;
                    } else {  
                        bytesSkipped = 1;
                    }  
                }  
                totalBytesSkipped += bytesSkipped;  
            }  
            return totalBytesSkipped;  
        }  
    }  
}
